package com.qd.tfpay.util;

import cn.hutool.crypto.SecureUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * 签名
 *
 * @author sjk
 */
@Slf4j
@Service
public class SignUtil {
    private static final String CHANNEL_DETAILS = "channel_details";
    private static final String PAY_FEE_DETAILS = "pay_fee_details";
    private static final String REFUND_FEE_DETAILS = "refund_fee_details";

    private SignUtil() {

    }

    private static ObjectMapper objectMapper;

    @Autowired
    public SignUtil(ObjectMapper objectMapper) {
        SignUtil.objectMapper = objectMapper;
    }

    @SneakyThrows
    public static String createReqJsonStr(Map<String, Object> param, String apiKey) {
        JsonNode channelDetails = null;
        if (null != param.get(CHANNEL_DETAILS)) {
            channelDetails = (JsonNode) param.get(CHANNEL_DETAILS);

            //构件签名字符串，把json中的：和“全部去掉
            param.remove(CHANNEL_DETAILS);
            String value = channelDetails.toString().replace("\"", "");
            value = value.replace(":", "=");
            param.put(CHANNEL_DETAILS, value);
        }
        //加入默认参数
        param.putIfAbsent("service_version", "1.1");
        param.put("input_charset", "UTF-8");
        //2.除去数组中的空值和签名参数
        Map<String, String> newParam = paraFilter(param);
        //3.把数组所有元素排序，并按照“参数=参数值”的模式用“&”字符拼接成字符串 + key
        final String linkString = createLinkString(newParam) + apiKey;
        if(log.isDebugEnabled()) {
            log.debug("apiKey: " + apiKey);
            log.debug("【MD5】加密前原始串：{}", linkString);
        }
        //4.生成签名串MD5
        //16进制  UTF-8  小写
        String sign = SecureUtil.md5(linkString);
        if(log.isDebugEnabled()) {
            log.debug("【MD5】加密后sign:{}", sign);
        }

        //5.请求报文加上 sign和sign_type
        newParam.put("sign", sign);
        newParam.put("sign_type", "MD5");
        if (null != param.get(CHANNEL_DETAILS)) {
            newParam.remove(CHANNEL_DETAILS);
        }

        final ObjectNode s = (ObjectNode) objectMapper.readTree(objectMapper.writeValueAsString(newParam));
        if (null != channelDetails) {
            s.set(CHANNEL_DETAILS, channelDetails);
        }

        //6.生成请求报文
        String jsonString = s.toString();
        if(log.isDebugEnabled()) {
            log.debug("请求报文：{}", jsonString);
        }
        return jsonString;
    }

    /**
     * 判断签名是否正确
     *
     * @param param  签名参数
     * @param apiKey key
     * @return true or false
     */
    @SneakyThrows
    public static boolean isSign(Map<String, Object> param, String apiKey) {
        //支付回执
        Object payFeeDetails = param.get(PAY_FEE_DETAILS);
        if (null != payFeeDetails) {
            param.remove(PAY_FEE_DETAILS);
            String value = payFeeDetails.toString().replace("\"", "");
            value = value.replace(":", "=");
            param.put(PAY_FEE_DETAILS, value);
        }
        //退款回执
        Object refundFeeDetails = param.get(REFUND_FEE_DETAILS);
        if (null != refundFeeDetails) {
            param.remove(REFUND_FEE_DETAILS);
            String value = refundFeeDetails.toString().replace("\"", "");
            value = value.replace(":", "=");
            param.put(REFUND_FEE_DETAILS, value);
        }

        //2.除去数组中的空值和签名参数
        Map<String, String> newParam = SignUtil.paraFilter(param);
        //3.把数组所有元素排序，并按照“参数=参数值”的模式用“&”字符拼接成字符串 + key
        final String linkString = SignUtil.createLinkString(newParam) + apiKey;
        if(log.isDebugEnabled()) {
            log.debug("【MD5】加密前原始串：{}", linkString);
        }
        //4.生成签名串MD5
        //16进制  UTF-8  小写
        String sign = SecureUtil.md5(linkString);
        if(log.isDebugEnabled()) {
            log.debug("【MD5】加密后sign:{}", sign);
        }
        final Object reqSign = param.get("sign");
        if (null == reqSign) {
            log.error("请求参数中sign为空");
            return false;
        }
        if (null != payFeeDetails) {
            param.put(PAY_FEE_DETAILS, objectMapper.readTree(payFeeDetails.toString()));
        }
        if (null != refundFeeDetails) {
            param.put(REFUND_FEE_DETAILS, objectMapper.readTree(refundFeeDetails.toString()));
        }
        return reqSign.toString().equals(sign);
    }

    /**
     * 除去数组中的空值和签名参数
     *
     * @param sArray 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    @SneakyThrows
    public static Map<String, String> paraFilter(Map<String, Object> sArray) {

        Map<String, String> result = new HashMap<>();

        if (sArray == null || sArray.isEmpty()) {
            return result;
        }
        for (String key : sArray.keySet()) {
            Object obj = sArray.get(key);
            String value;
            if (obj instanceof String) {
                value = String.valueOf(obj);
            } else {
                value = objectMapper.writeValueAsString(obj);
            }
            if ("null".equals(value) || value == null || value.isEmpty() || "sign".equalsIgnoreCase(key)
                    || "sign_type".equalsIgnoreCase(key)) {
                continue;
            }
            result.put(key, value);
        }

        return result;
    }

    /**
     * 把数组所有元素排序，并按照“参数=参数值”的模式用“&”字符拼接成字符串
     *
     * @param params 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params) {

        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);
        StringBuilder preStr = new StringBuilder();
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            // 拼接时，不包括最后一个&字符
            if (i == keys.size() - 1) {
                preStr.append(key).append("=").append(value);
            } else {
                preStr.append(key).append("=").append(value).append("&");
            }
        }
        return preStr.toString();
    }


}